package com.hua.customviews.widget

import android.content.Context
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.View
import androidx.recyclerview.widget.RecyclerView
import com.elvishew.xlog.XLog
import kotlin.math.abs
import kotlin.math.roundToInt

@Suppress("unused")
class SlideCheckRecyclerView(private val mContext: Context, attrs: AttributeSet?, defStyleAttr: Int) :
    RecyclerView(mContext, attrs, defStyleAttr) {
    init {
        initView()
    }

    constructor(mContext: Context, attrs: AttributeSet?) : this(mContext, attrs, 0) {
        initView()
    }

    constructor(mContext: Context) : this(mContext, null, 0) {
        initView()
    }

    private inline val Int.dp: Int get() = (this * mContext.resources.displayMetrics.density + 0.5f).roundToInt()
    private var onItemHoverListener: ((direction: Int, position: Int) -> Unit)? = null
    private var lastMoveY = 0f
    private var lastPosition = -1
    private var lastDirection = -1

    private var isCheckEvent = false
    private var isClick = true
    private var minChildHeight = 0
    private var slideCheckWidth = if (isInEditMode) 200 else 72.dp

    companion object {
        const val UP = -1
        const val DOWN = 1
    }

    private fun initView() {
    }

    override fun onViewAdded(child: View?) {
        super.onViewAdded(child)
        //当更新列表时检查最小item高度并更新
        val childHeight = child?.height ?: 0
        if (childHeight > 0 && (childHeight < minChildHeight || minChildHeight == 0))
            minChildHeight = childHeight
    }

    override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
        if (ev != null && ev.x < slideCheckWidth) fixLoss(ev, this::computeMove)
        when (ev?.action) {
            MotionEvent.ACTION_DOWN -> {
                lastMoveY = ev.y
                //判断是否为设置区域滑动
                isCheckEvent = ev.x < slideCheckWidth
                //起始认为是点击事件
                isClick = true
            }

            MotionEvent.ACTION_MOVE -> {
                //发生滑动不是点击事件
                isClick = false
                //如果在设置区域内滑动则处理相关滑动逻辑
                if (isCheckEvent) {
                    val moveX = ev.x
                    val moveY = ev.y
                    computeMove(moveX, moveY)
                }
            }

            MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
                lastPosition = -1
            }
        }
        //区域内滑动消耗事件否则不消耗
        return if (ev != null && isCheckEvent && !isClick) true
        else super.dispatchTouchEvent(ev)
    }

    private var lastEvX = 0f
    private var lastEvY = 0f
    private val fixInterval = 20f

    /**
     * 定长补差，根据固定补差间隙进行补差
     *
     */
    private fun fixLoss(ev: MotionEvent, fixLoss: (fixX: Float, fixY: Float) -> Unit) {
        if (ev.action != MotionEvent.ACTION_DOWN) {
            var xChange = abs(ev.x - lastEvX)
            val isXLost = xChange >= fixInterval

            var yChange = abs(ev.y - lastEvY)
            val isYLost = yChange >= fixInterval

            if (isXLost || isYLost) {
                while (xChange > 0 || yChange > 0) {
                    val fixX =
                        lastEvX + if (xChange < fixInterval) 0f
                        else if (ev.x > lastEvX) fixInterval
                        else -fixInterval
                    val fixY =
                        lastEvY + if (yChange < fixInterval) 0f
                        else if (ev.y > lastEvY) fixInterval
                        else -fixInterval
                    fixLoss.invoke(fixX, fixY)
                    if (xChange > 0) xChange -= fixInterval
                    if (yChange > 0) yChange -= fixInterval
                    lastEvX = fixX
                    lastEvY = fixY
                }
            }
        }
        lastEvX = ev.x
        lastEvY = ev.y
    }

    /**
     * 当在允许范围内滑过item时产生OnItemHover回调
     */
    fun setOnItemHoverListener(listener: (direction: Int, position: Int) -> Unit) {
        onItemHoverListener = listener
    }

    /**
     * 当前滑动方向
     */
    private val Float.moveDirection get() = if (this < lastMoveY) UP else DOWN

    /**
     * 获取具体坐标位置的item在Adapter中的位置
     * @see findChildViewUnder
     * @see getChildAdapterPosition
     */
    private fun getPositionAt(x: Float, y: Float): Int {
        findChildViewUnder(x, y)?.let {
            return getChildAdapterPosition(it)
        }
        return -1
    }

    /**
     * 判断滑动情况并产生onItemHover回调
     * @param moveX x坐标
     * @param moveY y坐标
     * @invoke onItemHoverListener
     */
    private fun computeMove(moveX: Float, moveY: Float) {
        val moveDirection = moveY.moveDirection
        //是否滑到顶/底
        val isCanScroll = canScrollVertically(moveDirection)
        //是否进行自动滚动
        if ((moveY > height || moveY < 0) && isCanScroll)
            scrollBy(
                moveX.toInt(),
                (if (moveY < 0) moveY else moveY - height).toInt()
            )
        //获取当前坐标Position
        val position = getPositionAt(
            moveX,
            if (moveY < 0 && isCanScroll) 0f
            else if (moveY > height && isCanScroll) height.toFloat()
            else moveY
        )

        XLog.i("----->RvMove: $moveY direction:${moveDirection} pos:$position ")
        //如果滑过了一个item或者滑动换向，则触发监听
        if ((lastPosition != position || lastDirection != moveDirection) && position != -1) {
            onItemHoverListener?.invoke(moveDirection, position)
            lastPosition = position
            lastDirection = moveDirection
//            XLog.i("----->RvFind:$moveY direction:${moveDirection} pos:$position")
        }

//        fixLoss(moveX, moveY, position, moveDirection)

        lastMoveY = moveY
    }

    private var overMoveY = 0f
    private var overMovePosition = -1
    private var fixTimes = 0

    /**
     * 按照滑动差异及最小item高度进行计算补差
     */
    private fun fixLoss(moveX: Float, moveY: Float, position: Int, moveDirection: Int) {
//        检测是否有漏掉的item（当滑动足够快时，MotionEvent间会差很多,可能跳过item） 记录滑动差异大于最小item高度的纵坐标和位置
        if (abs(moveY - lastMoveY) > minChildHeight && minChildHeight > 0 && overMoveY == 0f) {
            overMoveY = moveY
            overMovePosition = position
            //此时补差起始坐标为lastMoveY 结束坐标为overMoveY

//            val lastY = lastMoveY //<-这个lastY只是用于日志打印
            //如果已经补差了一个item才进行更新上次坐标，为了第一次补差时使用的是漏掉的前一次滑动位置开始
            if (fixTimes > 0) lastMoveY = moveY
            fixTimes++  //记录本次补差次数

            //根据当前滑动方向计算需要修复的坐标及位置（补差起始位置 ± 最小item高度）
            val fixY = lastMoveY + if (moveDirection == UP) -minChildHeight else minChildHeight
            val fixPos = getPositionAt(moveX, fixY)
//            XLog.i("----->RvCompute: $moveY pos:$position lastMove:$lastY fix:$fixY " +
//                    "fixPos:$fixPos times:$fixTimes ")
            //当修复的坐标位置 与 当前位置和漏掉时滑动的位置均不一致时（证明中间确实漏掉了）才进行补差 否则 结束本次补差
            if (fixPos != -1 && fixPos != position && fixPos != overMovePosition)
                computeMove(moveX, fixY)
            else {
                overMoveY = 0f
                fixTimes = 0
                overMovePosition = -1
            }
        }
    }
}